package com.nulldev.util.uri.yt.broken.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import com.nulldev.util.lang.json.Json;
import com.nulldev.util.lang.json.JsonValue;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.string.StringsUtil;
import com.nulldev.util.uri.URIUtil;
import com.nulldev.util.uri.URIUtil2;
import com.nulldev.util.uri.client.HttpClient;

import com.nulldev.util.uri.http.HTTP;
import com.nulldev.util.uri.yt.broken.FmtStreamMap;
import com.nulldev.util.uri.yt.broken.Resolution;
import com.nulldev.util.uri.yt.broken.ResolutionNote;

@Deprecated
public class YTD2Util {
    public static final String BASEURL = "https://www.youtube.com/";
    public static final String BASEURL_NEL = "https://www.youtube.com";
    public static final String WATCHV = "https://www.youtube.com/watch?v=%s";
    private static final String USER_AGENT = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
    private static final String JSPLAYER = "ytplayer\\.config\\s*=\\s*([^\\n]+);";
    private static final String FUNCCALL = "([$\\w]+)=([$\\w]+)\\(((?:\\w+,?)+)\\)$";
    private static final String OBJCALL = "([$\\w]+).([$\\w]+)\\(((?:\\w+,?)+)\\)$";
    private static final String[] REGEX_PRE = {"*", ".", "?", "+", "$", "^", "[", "]", "(", ")", "{", "}", "|", "\\", "/"};
    
    public static String parseDownloadUrl(final FmtStreamMap fmtStreamMap) {
        String downloadUrl = null;
        if (!StringsUtil.isEmpty(fmtStreamMap.sig)) {
            String sig = fmtStreamMap.sig;
            downloadUrl = String.format("%s&signature=%s", fmtStreamMap.url, sig);
        } else {
            String jsContent = getContent(fmtStreamMap.html5playerJS);
            downloadUrl = (decipher(jsContent, fmtStreamMap));
        }
        return downloadUrl;
    }
    
    public static List<FmtStreamMap> fetchYoutube(final String vid) {
        String watchUrl = String.format(WATCHV, vid);
        //System.out.println(watchUrl + " | " + vid);
        //final HttpClient cl = new HttpClient();
        try {
			//final HTTPGet x = cl.queryGet(URIUtil.safeCreate(watchUrl));
        	final String loaded = URIUtil.downloadPageS(URIUtil.safeCreate(watchUrl), URIUtil2.URI_UTIL2_USER_AGENT);
        	//System.out.println("loaded() -> " + loaded);
			return call(loaded); //call(x.getResult());
		} catch (Exception e) {
			e.printStackTrace();
			return new ArrayList<FmtStreamMap>();
		}
    }
    
    public static List<FmtStreamMap> call(String pageContent) {
        try {
        	//System.out.println(pageContent);
            Pattern jsPattern = Pattern.compile(JSPLAYER, Pattern.MULTILINE);
            Matcher matcher = jsPattern.matcher(pageContent);
            if (matcher.find()) {
            	//JVMUtil.async_println(Json.formatJSONStr(matcher.group(1), 4));
                JsonValue ytplayerConfig = Json.parseDEC(matcher.group(1)); //Json.object().add("idk", matcher.group(1)); //new JsonObject(matcher.group(1));
                JsonValue args = ytplayerConfig.asObject().get("args");

                String html5playerJS = ytplayerConfig.asObject().get("assets").asObject().getString("js", "N/A");
                if (html5playerJS.startsWith("//")) {
                    html5playerJS = "http:" + html5playerJS;
                } else if (html5playerJS.startsWith("/")) {
                    html5playerJS = BASEURL_NEL + html5playerJS;
                } else {
                	LoggerFactory.getLogger().warn("call(pgc) -> html5PlayerJS is not a link!");
                }
                //System.out.println("config -> " + html5playerJS);

                //Log.d("Html5PlayerJS", "htmljs:" + html5playerJS);
                String fmtStream = args.asObject().getString("url_encoded_fmt_stream_map", "N/A");
               // System.out.println("fmtStream = " + fmtStream);

                String[] fmtArray = fmtStream.split(",");
                
                List<FmtStreamMap> streamMaps = new ArrayList<FmtStreamMap>();
                for (String fmt : fmtArray) {
                    FmtStreamMap parseFmtStreamMap = parseFmtStreamMap(new Scanner(fmt), "utf-8");
                    parseFmtStreamMap.html5playerJS = html5playerJS;
                    parseFmtStreamMap.videoid = args.asObject().getString("video_id", "N/A");
                    parseFmtStreamMap.title = args.asObject().getString("title", "N/A");
                    if (parseFmtStreamMap.resolution != null) {
                        streamMaps.add(parseFmtStreamMap);
                    }
                }

                String adaptiveStream = args.asObject().getString("adaptive_fmts", "N/A");

                String[] adaptiveStreamArray = adaptiveStream.split(",");
                
                for (String fmt : adaptiveStreamArray) {
                    FmtStreamMap parseFmtStreamMap = parseFmtStreamMap(new Scanner(fmt), "utf-8");
                    parseFmtStreamMap.html5playerJS = html5playerJS;
                    parseFmtStreamMap.videoid = args.asObject().getString("video_id", "N/A");
                    parseFmtStreamMap.title = args.asObject().getString("title", "N/A");
                    if (parseFmtStreamMap.resolution != null) {
                        streamMaps.add(parseFmtStreamMap);
                    }
                }

                return streamMaps;
            }
        } catch (Exception ex) {
        	ex.printStackTrace();
        }
        LoggerFactory.getLogger().error("call() -> Hit fallback, returning nothing!");
        return new ArrayList<FmtStreamMap>();
    }
    
    public static FmtStreamMap parseFmtStreamMap(final Scanner scanner, final String encoding) {
        FmtStreamMap streamMap = new FmtStreamMap();
        scanner.useDelimiter(PARAMETER_SEPARATOR);
        while (scanner.hasNext()) {
            final String[] nameValue = scanner.next().split(NAME_VALUE_SEPARATOR);
            if (nameValue.length == 0 || nameValue.length > 2) {
                throw new IllegalArgumentException("bad parameter");
            }

            final String name = decode(nameValue[0], encoding);
            String value = null;
            if (nameValue.length == 2) {
                value = decode(nameValue[1], encoding);
            }
            //Log.d("YoutubeUtils", "name:" + name + ",values:" + value);

            if (StringsUtil.equals("fallback_host", name)) {
                streamMap.fallbackHost = value;
            }
            if (StringsUtil.equals("s", name)) {
                streamMap.s = value;
            }
            if (StringsUtil.equals("itag", name)) {
                streamMap.itag = value;
            }
            if (StringsUtil.equals("type", name)) {
                streamMap.type = value;
            }
            if (StringsUtil.equals("quality", name)) {
                streamMap.quality = value;
            }
            if (StringsUtil.equals("url", name)) {
                streamMap.url = value;
            }
            if (StringsUtil.equals("sig", name)) {
                streamMap.sig = value;
            }
            if (StringsUtil.equals("signature", name)) {

            }
            if (!StringsUtil.isEmpty(streamMap.itag)) {
                streamMap.resolution = Resolutions.get(streamMap.itag);
            }
            if (streamMap.resolution != null) {
                streamMap.extension = streamMap.resolution.format;
            }

        }
        return streamMap;
    }
    
    public static final List<Resolution> playResolutions = new ArrayList<Resolution>();
    public static final HashMap<String, Resolution> Resolutions = new HashMap<String, Resolution>();

    static {
    	if (playResolutions.isEmpty()) {
            playResolutions.add(new Resolution("17", "176x144", "3gp", "normal", ResolutionNote.LHD));
            playResolutions.add(new Resolution("36", "320x240", "3gp", "normal", ResolutionNote.LHD));
            playResolutions.add(new Resolution("18", "640x360", "mp4", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("242", "360x240", "webm", "normal", ResolutionNote.LHD));
            playResolutions.add(new Resolution("242", "360x240", "webm", "normal", ResolutionNote.LHD));
            playResolutions.add(new Resolution("243", "480x360", "webm", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("243", "480x360", "webm", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("43", "640x360", "webm", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("244", "640x480", "webm", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("245", "640x480", "webm", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("167", "640x480", "webm", "video", ResolutionNote.MHD));
            playResolutions.add(new Resolution("246", "640x480", "webm", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("247", "720x480", "webm", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("44", "854x480", "webm", "normal", ResolutionNote.MHD));
            playResolutions.add(new Resolution("168", "854x480", "webm", "video", ResolutionNote.MHD));
    	}
    	if (Resolutions.isEmpty()) {
            Resolutions.put("5", new Resolution("5", "400x240", "flv", "normal", ResolutionNote.LHD));//
            Resolutions.put("6", new Resolution("6", "450x270", "flv", "normal", ResolutionNote.MHD));
            Resolutions.put("17", new Resolution("17", "176x144", "3gp", "normal", ResolutionNote.LHD));//
            Resolutions.put("18", new Resolution("18", "640x360", "mp4", "normal", ResolutionNote.MHD));
            Resolutions.put("22", new Resolution("22", "1280x720", "mp4", "normal", ResolutionNote.HD));
            Resolutions.put("34", new Resolution("34", "640x360", "flv", "normal", ResolutionNote.MHD));
            Resolutions.put("35", new Resolution("35", "854x480", "flv", "normal", ResolutionNote.MHD));
            Resolutions.put("36", new Resolution("36", "320x240", "3gp", "normal", ResolutionNote.LHD));//
            Resolutions.put("37", new Resolution("37", "1920x1080", "mp4", "normal", ResolutionNote.XLHD));
            Resolutions.put("38", new Resolution("38", "4096x3072", "mp4", "normal", ResolutionNote.XLHD));
            Resolutions.put("43", new Resolution("43", "640x360", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("44", new Resolution("44", "854x480", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("45", new Resolution("45", "1280x720", "webm", "normal", ResolutionNote.HD));
            Resolutions.put("46", new Resolution("46", "1920x1080", "webm", "normal", ResolutionNote.XLHD));
            Resolutions.put("167", new Resolution("167", "640x480", "webm", "video", ResolutionNote.MHD));
            Resolutions.put("168", new Resolution("168", "854x480", "webm", "video", ResolutionNote.MHD));
            Resolutions.put("169", new Resolution("169", "1280x720", "webm", "video", ResolutionNote.HD));
            Resolutions.put("170", new Resolution("170", "1920x1080", "webm", "video", ResolutionNote.XLHD));
            Resolutions.put("242", new Resolution("242", "360x240", "webm", "normal", ResolutionNote.LHD));//
            Resolutions.put("243", new Resolution("243", "480x360", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("244", new Resolution("244", "640x480", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("245", new Resolution("245", "640x480", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("246", new Resolution("246", "640x480", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("247", new Resolution("247", "720x480", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("82", new Resolution("82", "360p", "mp4", "normal", ResolutionNote.MHD));
            Resolutions.put("83", new Resolution("83", "480p", "mp4", "normal", ResolutionNote.MHD));
            Resolutions.put("84", new Resolution("84", "720p", "mp4", "normal", ResolutionNote.MHD));
            Resolutions.put("85", new Resolution("85", "1080p", "mp4", "normal", ResolutionNote.MHD));
            Resolutions.put("100", new Resolution("100", "360p", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("101", new Resolution("101", "480p", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("102", new Resolution("102", "720p", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("139", new Resolution("139", "Audio only", "m4a", "normal", ResolutionNote.MHD));
            Resolutions.put("140", new Resolution("140", "Audio only", "m4a", "normal", ResolutionNote.MHD));
            Resolutions.put("141", new Resolution("141", "Audio only", "m4a", "normal", ResolutionNote.MHD));
            Resolutions.put("171", new Resolution("313", "Audio only", "webm", "normal", ResolutionNote.MHD));
            Resolutions.put("172", new Resolution("313", "Audio only", "webm", "normal", ResolutionNote.MHD));
    	}
    }
    
    private static final String PARAMETER_SEPARATOR = "&";
    private static final String NAME_VALUE_SEPARATOR = "=";
    
    public static String decode(final String content, final String encoding) {
        try {
            return URLDecoder.decode(content, encoding != null ? encoding : HTTP.DEFAULT_CONTENT_CHARSET);
        } catch (UnsupportedEncodingException problem) {
            throw new IllegalArgumentException(problem);
        }
    }
    
    private static String decipher(String jsContent, FmtStreamMap fmtStreamMap) {
        String f1 =
            getRegexString(jsContent, "\\w+\\.sig\\|\\|([$a-zA-Z]+)\\([$a-zA-Z]+\\ .[$a-zA-Z]+\\)");
        if (StringsUtil.isEmpty(f1)) {
            f1 = getRegexString(jsContent,
                    "\\w+\\.sig.*?\\?.*&&\\w+\\.set\\(\\\"signature\\\",([$a-zA-Z]+)\\([$a-zA-Z]+\\"
                        + ".[$a-zA-Z]+\\)\\)");
        }
        String finalF1 = f1;

        for (String aREGEX_PRE : REGEX_PRE) {
        	if (aREGEX_PRE == null) {
        		LoggerFactory.getLogger().error("decipherContent(js, fmtSM) -> aREGEX_PRE was null!");
        	}else if (f1 == null) {
        		LoggerFactory.getLogger().error("decipherContent(js, fmtSM) -> f1 was null!");
        	}else if (f1.contains(aREGEX_PRE)) {
                finalF1 = "\\" + f1;
                break;
            }
        }
        String f1def =
            getRegexString(jsContent, String.format(
                "((function\\s+%s|[{;,]%s\\s*=\\s*function|var\\s+%s\\s*=\\s*function\\s*)\\([^)]*\\)"
                    + "\\s*\\{[^\\{]+\\})",
                finalF1, finalF1, finalF1));

        if (f1def.startsWith(",")) {
            f1def = f1def.replaceFirst(",", "");
        }

        StringBuilder functionSb = new StringBuilder();
        trJs(f1def, jsContent, functionSb);

        if (functionSb.length() > 0) {
            String jsStr = functionSb.toString() + "\n" + String.format("%s('%s')", f1, fmtStreamMap.s);

            ScriptEngineManager engineManager = new ScriptEngineManager();
            ScriptEngine engine = engineManager.getEngineByName("nashorn");
            try {
                String sig = (String) engine.eval(jsStr);
                return String.format("%s&signature=%s", fmtStreamMap.url, sig);
            }catch (Exception e) {
            	e.printStackTrace();
        	}finally {
            }
        }
        return null;
    }

    
    private static URLConnection openConnection(URL localURL) throws IOException {
        URLConnection connection;
        connection = localURL.openConnection();
        return connection;
    }
    
    public static String getContent(String url) {
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader reader = null;
        StringBuilder resultBuffer = new StringBuilder();
        String tempLine = null;
        try {
            URL localURL = new URL(url);

            URLConnection connection = openConnection(localURL);
            HttpURLConnection httpURLConnection = (HttpURLConnection) connection;

            if (httpURLConnection.getResponseCode() >= 300) {
                throw new Exception(
                    "HTTP Request wasn't successful, Response code is " + httpURLConnection.getResponseCode());
            }

            inputStream = httpURLConnection.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream);
            reader = new BufferedReader(inputStreamReader);

            while ((tempLine = reader.readLine()) != null) {
                resultBuffer.append(tempLine);
            }

        } catch (Exception ex) {
        } finally {

            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        return resultBuffer.toString();
    }
    
    private static void trJs(String jsfunction, String jsContent, StringBuilder functionSb) {
        String[] split = jsfunction.split(";");
        Pattern funcPattern = Pattern.compile(FUNCCALL);
        Pattern objPattern = Pattern.compile(OBJCALL);
        Matcher matcher = null;
        for (String code : split) {
            String innerFuncCall = null;
            matcher = objPattern.matcher(code);
            if (matcher.matches()) {
                String strObj, strFuncName, strArgs;
                strObj = matcher.group(1);
                strFuncName = matcher.group(2);
                strArgs = matcher.group(3);
                if (!StringsUtil.isEmpty(strObj)) {
                    jsfunction = jsfunction.replace(strObj + ".", "");
                }
                String objFunction = "(" + strFuncName + "\\s*:\\s*function\\(.*?\\)\\{[^\\{]+\\})";
                String f1def = getRegexString(jsContent, objFunction);

                if (!StringsUtil.isEmpty(f1def)) {
                    String objFuncMain = "function ";
                    f1def = f1def.replace(":function", "");
                    f1def = f1def.replace("}}", "}");
                    objFuncMain += f1def;
                    functionSb.append(objFuncMain);
                    functionSb.append("\n");
                }
            }

            matcher = funcPattern.matcher(code);
            if (matcher.matches()) {
                String strFunName, strArgs;
                strFunName = matcher.group(2);
                if (!StringsUtil.isEmpty(strFunName)) {
                    strFunName = Pattern.quote(strFunName);
                }
                strArgs = matcher.group(3);
                if (!StringsUtil.isEmpty(strArgs)) {
                    String[] args = strArgs.split(",");
                    if (args.length == 1) {
                        innerFuncCall = String.format("(function %s\\(\\w+\\)\\{[^\\{]+\\})", strFunName);
                    } else {
                        innerFuncCall = String.format("(function %s\\(", strFunName);
                        for (int i = 0; i < args.length - 1; ++i) {
                            innerFuncCall += "\\w+,";
                        }
                        innerFuncCall += "\\w+\\)\\{[^\\{]+\\})";
                    }
                }
                if (!StringsUtil.isEmpty(innerFuncCall)) {

                    String f1def = getRegexString(jsContent, innerFuncCall);
                    functionSb.append(f1def);
                    functionSb.append("\n");
                }
            }

        }
        functionSb.append(jsfunction);
    }

    public static String getRegexString(String content, String pattern) {
    	//System.out.println("getRegexString(cnt, ptn) -> cnt=" + content.substring(0, 10) + ",ptn=" + pattern);
        Pattern p = Pattern.compile(pattern);
        Matcher matcher = p.matcher(content);
        //String group = null;
        if (matcher.find()) {
           // LogUtil.d("group:" + matcher.group(0));
            return matcher.group(1);
        }
       // if (group == "a() {}" || group == content) {
       // 	LoggerFactory.getLogger().warn("getRegexString(cnt, ptn) -> group == null!");
       // }
        return "a(i){i = 0;}";
    }
    
    public static void main(String[] args) {
    	ArrayUtils.printLln(fetchYoutube("https://www.youtube.com/watch?v=iJUM11goXAU"));
    }
}
